﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace KinectServer.Module
{
    public abstract class UdpServer
    {
        internal enum State
        {
            Accepting,
            Communicating
        }

        private bool _isRunning = false;
        private State _state = State.Accepting;
        private Thread _clientThread = null;
        private int _port = 0;
        private string _ip = "192.168.0.3";

        public UdpServer(int port = 9100)
        {
            this._port = port;
        }

        public Action ServerClosed { get; set; }


        public void Start(string ip, int port)
        {
            this._ip = ip;
            this._port = port;
            this.Start();
        }

        public void Start()
        {
            if (this._clientThread != null)
            {
                if (this._clientThread.IsAlive == true)
                {
                    Log.e(this, "서버를 시작하지 못했습니다. 서버가 아직 종료가 안된듯...");
                    return;
                }
            }

            /* 초기화 시작 */
            try
            {
                this._clientThread = new Thread(new ThreadStart(this.Run));
                this._clientThread.Start();
            }
            catch (Exception ex)
            {
                Log.e(this, "서버를 시작하지 못했습니다. - " + ex.Message);
            }
            
        }

        public void Stop()
        {
            this._isRunning = false;
        }

        /// <summary>
        /// Workhorse
        /// </summary>
        private void Run()
        {
            UdpClient socket = null;
            IPAddress address = IPAddress.Parse(this._ip);
            IPEndPoint ipep = new IPEndPoint(address, this._port);
           
            this._state = State.Accepting;
            this._isRunning = true;
            while (this._isRunning)
            {
                switch (this._state)
                {
                    case State.Accepting:
                        socket = this.OnAccepting(ipep);
                        Log.i(this, "네트워크로 데이터를 전송 시작합니다.");
                        this._state = State.Communicating;
                        break;

                    case State.Communicating:
                        bool canSent = this.OnCommunicating(socket, ipep);
                        if (canSent == false)
                        {
                            this._isRunning = false;
                        }
                        break;

                    default:
                        break;
                }
            }

            if (socket != null)
            {
                socket.Close();
            }

            if (this.ServerClosed != null)
            {
                this.ServerClosed();
            }

            Log.i(this, "Server closed!");
        }

        private UdpClient OnAccepting(IPEndPoint ep)
        {
            try
            {   
                UdpClient udpServer = new UdpClient();             
                return udpServer;
            }
            catch (Exception ex)
            {
                Log.e(this.GetType(), ex.ToString());
                return null;
            }
        }

        private bool OnCommunicating(UdpClient socket, IPEndPoint ipep)
        {
            try
            {
                SendStream(socket, this.OnGetColorData(), ipep);
                SendStream(socket, this.OnGetDepthData(), ipep);
                return true;
            }
            catch (ObjectDisposedException ex)
            {
                Log.e(this, "Client is Closed");
                Log.e(this, ex.ToString());
                return false;
            }
            catch (Exception ex)
            {
                Log.e(this, "Error while sending datagram");
                Log.e(this, ex.ToString());
                return false;
            }
        }

        private bool SendStream(UdpClient socket, MemoryStream stream, IPEndPoint ipep)
        {
            if (stream != null && stream.Length > 0)
            {
                byte[] datagram = stream.ToArray();
                socket.Send(datagram, datagram.Length, ipep);
                return true;
            }
            return false;
        }

        protected abstract MemoryStream OnGetColorData();

        protected abstract MemoryStream OnGetDepthData();

    }
}
